﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;


/// <summary>
/// 读取BMP图片
/// </summary>
/// <auther></auther>
/// <msn></msn>
/// <remarks>
/// 仅以此说明BMP文件格式
/// 程序没有进行优化.只是说明了算法
///</remarks>
class ShowBMP
{

    #region 构造函数
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="strBmpFileName"></param>
    public ShowBMP(String strBmpFileName)
    {
        this.ReadFile(strBmpFileName);
    }
    #endregion
    #region 字段
    private readonly string BMP = "BM";
    private readonly string GIF = "GIF";
    private readonly string JPG = "JFIF";
    byte[] flb; //读出来的字节信息
    UInt64[,] p;//图片色信息的二维数据表.X,Y 分别是作标

    ImageFileHeader ifh = new ImageFileHeader();
    IamgeHeader ih = new IamgeHeader();
    RGBPalette rgb = new RGBPalette();
    //http://hi.baidu.com/xianle
    #endregion

    #region 结构体定义

    #region 图片格式 ImageFileType
    /// <summary>
    /// 图片格式
    /// </summary>
    public enum ImageFileType
    {
        BMP,
        GIF,
        JPG,
        Other
    }
    #endregion

    #region 文件头信息 ImageFileHeader
    /// <summary>
    /// 文件头信息
    /// </summary>
    public struct ImageFileHeader
    {
        public ImageFileType FileType;//文件类型
        public UInt32 FileSize;//全部字节数
        public UInt16 Reserved1;//保留
        public UInt16 Reserved2;//保留
        public UInt32 OffBits;//文件头信息+图头信息+调色板信息的字节数
    }//总长14字节
    #endregion

    #region 图的头信息 IamgeHeader
    /// <summary>
    /// 图的头信息
    /// </summary>
    public struct IamgeHeader
    {
        public UInt32 Size;//图信息头的长度，为40。
        public Int32 Width;//指定图象的宽度，单位是象素。
        public Int32 Height;//指定图象的高度，单位是象素
        public UInt16 Planes;//必须是1，不用考虑
        public UInt16 BitCount;//指定表示颜色时要用到的位数，常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图)(新的.bmp格式支持32位色
        public UInt32 Compression;//指定位图是否压缩，有效的值为BI_RGB，BI_RLE8，BI_RLE4，BI_BITFIELDS(都是一些Windows定义好的常量)。要说明的是，Windows位图可以采用RLE4
        //和RLE8的压缩格式，但用的不多。我们今后所讨论的只有第一种不压缩的情况，即biCompression为BI_RGB的情况
        public UInt32 SizeImage;//指定实际的位图数据占用的字节数，其实也可以从以下的公式中计算出来 biSizeImage=biWidth’ × biHeight
        //上述公式中的biWidth’必须是4的整倍数
        public Int32 XPelsPerMeter;//指定目标设备的水平分辨率，单位是每米的象素个数
        public Int32 YPelsPerMeter;//指定目标设备的垂直分辨率
        public UInt32 ClrUsed;//指定本图象实际用到的颜色数，如果该值为零，则用到的颜色数为2biBitCount
        public UInt32 ClrImportant;//指定本图象中重要的颜色数，如果该值为零，则认为所有的颜色都是重要的
    }//长40字节
    #endregion

    #region 调色板Palette RGBPalette
    /// <summary>
    /// 调色板Palette
    /// </summary>
    /// <remarks>第三部分</remarks>
    public struct RGBPalette
    {
        public byte rgbBlue;//该颜色的蓝色分量
        public byte rgbGreen;//该颜色的绿色分量
        public byte rgbRed;//该颜色的红色分量
        public byte rgbReserved;//保留值
    }
    #endregion

    #endregion

    #region 属性
    public ImageFileHeader FileHeader
    {
        get
        {
            return this.ifh;
        }
    }
    public IamgeHeader ImgHeader
    {
        get
        {
            return this.ih;
        }
    }
    public RGBPalette RGB
    {
        get
        {
            return this.rgb;
        }
    }
    public UInt64[,] PIC
    {
        get
        {
            return this.p;
        }
    }
    #endregion


    #region 方法
    #region 测试方法 Start
    /// <summary>
    /// 测试方法
    /// </summary>
    public void Start()
    {
        this.ReadBF();
        this.ReadIH();
        this.ReadPalette();
        this.ReadImage();
    }
    #endregion

    #region 读取文件头信息 ReadBF
    /// <summary>
    /// 读取文件头信息
    /// </summary>
    /// <remarks>没做异常处理</remarks>
    private void ReadBF()
    {

        //测试文件类型
        StringBuilder sb = new StringBuilder();
        sb.Append((char)flb[0]);
        sb.Append((char)flb[1]);
        if (sb.ToString().Equals(this.BMP))
        {
            ifh.FileType = ImageFileType.BMP;
        }
        else
        {
            sb.Append((char)flb[2]);
            if (sb.ToString().Equals(this.GIF))
            {
                ifh.FileType = ImageFileType.GIF;
            }
            else
            {
                sb = new StringBuilder();
                sb.Append((char)flb[7]);
                sb.Append((char)flb[8]);
                sb.Append((char)flb[9]);
                sb.Append((char)flb[10]);
                if (sb.ToString().Equals(this.JPG))
                {
                    ifh.FileType = ImageFileType.JPG;
                }
                else ifh.FileType = ImageFileType.Other;
            }
        }

        //注入文件信息
        if (ifh.FileType == ImageFileType.BMP)
        {
            //注意.2进制.在WINDOWS下.是高位在后,低位在前的
            //先转成16进制.把高位和低位组合后.再转成10进制的,用来显示
            ifh.FileSize = UInt32.Parse((flb[5].ToString("X") + flb[4].ToString("X") + flb[3].ToString("X") + flb[2].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
            ifh.Reserved1 = UInt16.Parse((flb[7].ToString("X") + flb[6].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
            ifh.Reserved2 = UInt16.Parse((flb[9].ToString("X") + flb[8].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
            ifh.OffBits = UInt32.Parse((flb[13].ToString("X") + flb[12].ToString("X") + flb[11].ToString("X") + flb[10].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        }
    }
    #endregion

    #region 读取图头信息 ReadIH
    /// <summary>
    /// 读取图头信息
    /// </summary>
    private void ReadIH()
    {
        //方法比较麻烦..不过这样看得清楚
        ih.Size = UInt32.Parse((flb[17].ToString("X") + flb[16].ToString("X") + flb[15].ToString("X") + flb[14].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.Width = Int32.Parse((flb[21].ToString("X") + flb[20].ToString("X") + flb[19].ToString("X") + flb[18].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.Height = Int32.Parse((flb[25].ToString("X") + flb[24].ToString("X") + flb[23].ToString("X") + flb[22].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.Planes = UInt16.Parse((flb[27].ToString("X") + flb[26].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.BitCount = UInt16.Parse((flb[29].ToString("X") + flb[28].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.Compression = UInt32.Parse((flb[33].ToString("X") + flb[32].ToString("X") + flb[31].ToString("X") + flb[30].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.SizeImage = UInt32.Parse((flb[37].ToString("X") + flb[36].ToString("X") + flb[35].ToString("X") + flb[34].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.XPelsPerMeter = Int32.Parse((flb[41].ToString("X") + flb[40].ToString("X") + flb[39].ToString("X") + flb[38].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.YPelsPerMeter = Int32.Parse((flb[45].ToString("X") + flb[44].ToString("X") + flb[43].ToString("X") + flb[42].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.ClrUsed = UInt32.Parse((flb[49].ToString("X") + flb[48].ToString("X") + flb[47].ToString("X") + flb[46].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
        ih.ClrImportant = UInt32.Parse((flb[53].ToString("X") + flb[52].ToString("X") + flb[51].ToString("X") + flb[50].ToString("X")), System.Globalization.NumberStyles.AllowHexSpecifier);
    }
    #endregion

    #region 读取调色板信息 ReadPalette
    /// <summary>
    /// 读取调色板信息
    /// </summary>
    private void ReadPalette()
    {
        if (this.ifh.OffBits != 14 + this.ih.Size)
        {
            rgb.rgbBlue = flb[14 + this.ih.Size];
            rgb.rgbGreen = flb[14 + this.ih.Size + 1];
            rgb.rgbRed = flb[14 + this.ih.Size + 2];
            rgb.rgbReserved = flb[14 + this.ih.Size + 3];
        }
    }
    #endregion

    #region 读取文件 ReadFile ( String strBmpFileName )
    /// <summary>
    /// 读取文件
    /// </summary>
    /// <param name="strBmpFileName"></param>
    /// <returns></returns>
    private void ReadFile(String strBmpFileName)
    {
        flb = File.ReadAllBytes(strBmpFileName);
    }
    #endregion

    #region 读取图片信息 ReadImage
    /// <summary>
    /// 读取图片信息
    /// </summary>
    private void ReadImage()
    {

        p = new UInt64[ih.Width, ih.Height];
        UInt32 x = 0;
        UInt32 y = 0;
        UInt32 lastY = 0;//最后一行的行坐标
        UInt64 uiTotal = 0;//像素记数器
        StringBuilder sb = new StringBuilder();
        for (UInt32 i = ifh.OffBits; i < ifh.FileSize; i++)
        {

            sb.Append(Convert.ToString(this.flb[i], 2).PadLeft(8, '0'));

            switch (ih.BitCount)
            {
                case 2://黑白二色图
                case 1:
                case 0:
                    //一个字节可以表示8个象素
                    /// To Do:
                    for (int j = 0; j < 8; j++)
                    {
                        y = (UInt32)((UInt32)ih.Height - (uiTotal) / (UInt32)ih.Width - 1);
                        x = (UInt32)(uiTotal - (UInt32)((uiTotal) / (UInt32)ih.Width) * (UInt32)ih.Width);//图片信息数据是从下到上.从左到右放置的~所以在这里要做个转换

                        p[x, y] = Convert.ToUInt32(sb.ToString().Substring(j, 1), 2);//颜色信息的十进制数
                        uiTotal++;
                    }
                    sb = new StringBuilder();
                    break;
                case 4://16色图
                    //一个字节可以表示2个象素。
                    /// To Do:
                    for (int j = 0; j < 2; j++)
                    {
                        y = (UInt32)((UInt32)ih.Height - (uiTotal) / (UInt32)ih.Width - 1);
                        x = (UInt32)(uiTotal - (UInt32)((uiTotal) / (UInt32)ih.Width) * (UInt32)ih.Width);
                        p[x, y] = Convert.ToUInt32(sb.ToString().Substring(j * 4, 4), 2);//颜色信息的十进制数
                        uiTotal++;
                    }
                    sb = new StringBuilder();
                    break;
                case 16://256色
                    //一个字节可以表示1个象素。
                    /// To Do:
                    y = (UInt32)((UInt32)ih.Height - (uiTotal) / (UInt32)ih.Width - 1);
                    x = (UInt32)(uiTotal - (UInt32)((uiTotal) / (UInt32)ih.Width) * (UInt32)ih.Width);
                    p[x, y] = Convert.ToUInt32(sb.ToString(), 2);//颜色信息的十进制数
                    uiTotal++;
                    sb = new StringBuilder();
                    break;
                case 24://真彩色图
                    //三个字节才能表示1个象素
                    if (sb.Length == 24)
                    {
                        y = (UInt32)((UInt32)ih.Height - (uiTotal) / (UInt32)ih.Width - 1);
                        x = (UInt32)(uiTotal - (UInt32)((uiTotal) / (UInt32)ih.Width) * (UInt32)ih.Width);
                        p[x, y] = Convert.ToUInt32(sb.ToString(), 2);//颜色信息的十进制数
                        uiTotal++;
                        sb = new StringBuilder();
                        //数据宽存为4的整数倍.所以,在换行的时候.应排除几个没用的数据位,测试24位图是这样的
                        //其它的没做测试.可以自己试着改装一下
                        if (lastY != 0 && lastY != y)
                        {
                            i += (UInt32)ih.Width % 4;
                        }
                        lastY = y;
                    }
                    break;
                case 32://32位真彩色图
                    //4个字节才能表示1个象素
                    /// To Do:
                    if (sb.Length == 32)
                    {
                        y = (UInt32)((UInt32)ih.Height - (uiTotal) / (UInt32)ih.Width - 1);
                        x = (UInt32)(uiTotal - (UInt32)((uiTotal) / (UInt32)ih.Width) * (UInt32)ih.Width);
                        p[x, y] = Convert.ToUInt32(sb.ToString(), 2);//颜色信息的十进制数
                        uiTotal++;
                        sb = new StringBuilder();
                    }
                    break;
            }

        }
    }
    #endregion
    #endregion

}


